package foods

import (
	"hotel_Manager_Api/model/dbModel"
	"github.com/go-xorm/xorm"
	"hotel_Manager_Api/util"
	"github.com/go-martini/martini"
	"hotel_Manager_Api/model/dto"
	"github.com/martini-contrib/encoder"
	"net/http"
	"github.com/ahmetb/go-linq"
	"github.com/go-xorm/builder"
)

func NewFood(food dbModel.Food, db *xorm.Engine, idGenerator util.IdGenerator) (int, error) {
	food.Id = idGenerator.GenerateNewId()

	session := db.NewSession()
	defer session.Close()

	err := session.Begin()
	_, err = db.Insert(food)
	if err != nil {
		session.Rollback()
		return 500, err
	}

	rels := make([]dbModel.FoodTypeRel, len(food.TypeIds))
	linq.From(food.TypeIds).SelectT(func(typeId int32) dbModel.FoodTypeRel {
		return dbModel.FoodTypeRel{FoodId: food.Id, FoodTypeId: typeId}
	}).ToSlice(&rels)

	_, err = db.Insert(rels)
	if err != nil {
		session.Rollback()
		return 500, err
	}

	err = session.Commit()
	if err != nil {
		return 500, err
	}
	return 201, nil
}

func GetFoods(paramter martini.Params, page dto.PageCondition, req *http.Request, enc encoder.Encoder, db *xorm.Engine) ([]byte, error) {
	searchCondition := req.Form.Get("searchCondition")

	typeId, err := util.ParamaterToInt16(paramter, "typeId")
	if err != nil {
		return nil, err
	}

	var foodIds []int64
	if (typeId > 0) {
		typeRels := make([]dbModel.FoodTypeRel, 0)
		db.Where("foodTypeId=?", typeId).Find(&typeRels)
		foodIds = make([]int64, len(typeRels))
		linq.From(typeRels).SelectT(func(item dbModel.FoodTypeRel) int64 { return item.FoodId }).ToSlice(&foodIds)
	}

	foods := make([]dbModel.Food, 0)

	var fullConditions builder.Cond
	var condition1 = builder.Like{"name", searchCondition + "%"}
	if (typeId > 0) {
		condition2 := builder.Eq{"id": foodIds}
		fullConditions = builder.And(condition1, condition2)
	} else {
		fullConditions = condition1
	}

	totalCount, err := db.Where(fullConditions).Count(dbModel.Food{})

	db.Where(fullConditions).Desc("id").Limit(page.GetPageSizeAndOffset()).Find(&foods)

	foodTypeRels := make([]dbModel.FoodTypeRel, 0)
	db.Find(&foodTypeRels)
	rels := toFoodTypesRelsMap(foodTypeRels)
	for i, e := range foods {
		foods[i].TypeIds = rels[e.Id]
	}

	if err != nil {
		return nil, err
	}
	pageResult := page.GetPageResult(int(totalCount), foods)
	return encoder.Must(enc.Encode(pageResult)), nil
}

func ChangeFoodStatus(params martini.Params, db *xorm.Engine) (int, error) {
	id, err := util.ParamaterToInt64(params, "id")
	if (err != nil) {
		return 400, err
	}

	sql := "update `food`  set enable = !enable where id = ?"
	_, err = db.Exec(sql, id)
	if err != nil {
		return 500, err
	}
	return 200, nil
}

func EditFood(params martini.Params, food dbModel.Food, db *xorm.Engine) (int, error) {
	id, err := util.ParamaterToInt64(params, "id")
	if err != nil {
		return 500, err
	}
	food.Id = id

	session := db.NewSession()
	defer session.Close()
	oldRels := make([]dbModel.FoodTypeRel, 0)
	session.Where("foodId=?", id).Find(&oldRels)

	oldRelEquals := make([]util.Equalable, len(oldRels))
	for i, e := range oldRels {
		oldRelEquals[i] = e
	}

	newRelsEquals := make([]util.Equalable, len(food.TypeIds))
	for i, e := range food.TypeIds {
		newRelsEquals[i] = dbModel.FoodTypeRel{FoodId: id, FoodTypeId: e}
	}

	addRels, removeRels := util.CalculateChange(oldRelEquals, newRelsEquals)

	if len(addRels) > 0 {
		_, err = session.Insert(addRels)
		if err != nil {
			session.Rollback()
			return 500, err
		}
	}

	if len(removeRels) > 0 {
		_, err = session.ID(removeRels).Delete(dbModel.FoodTypeRel{})
		if err != nil {
			session.Rollback()
			return 500, err
		}
	}

	_, err = session.ID(id).Update(food)
	if err != nil {
		session.Rollback()
		return 500, err
	}
	session.Commit()
	return 200, nil
}

func DeleteFood(params martini.Params, db *xorm.Engine) (int, error) {
	id, err := util.ParamaterToInt64(params, "id")
	if err != nil {
		return 400, err
	}

	_, err = db.Id(id).Delete(dbModel.Food{})
	if (err != nil) {
		return 500, err
	}
	return 200, nil
}

func GetAllFoods(enc encoder.Encoder, db *xorm.Engine) ([]byte, error) {
	foodTypes := make([]dbModel.FoodType, 0)
	foodTypeRels := make([]dbModel.FoodTypeRel, 0)
	foods := make([]dbModel.Food, 0)
	tags := make([]dbModel.Tag, 0)
	units := make([]dbModel.Unit,0)

	db.Asc("orderId").Find(&foodTypes)
	db.Find(&foodTypeRels)
	db.Where("enable=?", true).Find(&foods)
	unitFoods := dto.FoodsToUnitFoods(foods)
	db.Find(&tags)
	db.Find(&units)

	result := joinFood(foodTypes, foodTypeRels, unitFoods, tags,units)

	return encoder.Must(enc.Encode(result)), nil
}

func joinFood(foodTypes []dbModel.FoodType, rels []dbModel.FoodTypeRel, foods []dto.UnitFood, tags []dbModel.Tag,units [] dbModel.Unit) []dto.FoodInFoodType {
	result := make([]dto.FoodInFoodType, len(foodTypes))
	relsMap := toTypeFoodsRelsMap(rels)
	foodMap := toFoodMap(foods)

	for i, e := range foodTypes {
		result[i] = convert2FoodInFoodType(e, relsMap, foodMap)
	}
	result = joinFoodTpesTag(result,tags)

	for _,e := range result{
		joinFoodUnits(e.Foods,units)
	}
	return result;
}

func joinFoodUnits(foods []dto.UnitFood,units [] dbModel.Unit) []dto.UnitFood {
	linq.From(foods).
		JoinT(
			linq.From(units),
			func(food dto.UnitFood) int16 {return food.UnitId},
			func(unit dbModel.Unit)int16 {return unit.Id},
			func(food dto.UnitFood,unit dbModel.Unit) dto.UnitFood {food.Unit=unit;return food}).
				ToSlice(&foods)
				return foods
}

func joinFoodTpesTag(food []dto.FoodInFoodType,foodTags []dbModel.Tag)[]dto.FoodInFoodType{
	linq.From(food).
		GroupJoinT(linq.From(foodTags),
		func(r dto.FoodInFoodType) int16 {
			return r.TagId
		},
		func(t dbModel.Tag) int16 {
			return t.Id
		},
		func(r dto.FoodInFoodType, t[] dbModel.Tag) dto.FoodInFoodType {
			if len(t)>0{
				r.Tag = t[0]
			}
			return r
		}).ToSlice(&food)
		return food
}

func toTypeFoodsRelsMap(source []dbModel.FoodTypeRel) map[int32][]int64 {
	result := make(map[int32][]int64)
	linq.From(source).
		GroupByT(func(item dbModel.FoodTypeRel) int32 { return item.FoodTypeId }, func(item dbModel.FoodTypeRel) int64 { return item.FoodId }).
		SelectT(func(g linq.Group) linq.KeyValue {
		ints := make([]int64, 0)
		linq.From(g.Group).SelectT(func(item interface{}) int64 { return item.(int64) }).ToSlice(&ints)
		return linq.KeyValue{Key: g.Key, Value: ints}
	}).
		ToMap(&result)
	return result
}

func toFoodTypesRelsMap(source []dbModel.FoodTypeRel) map[int64][]int32 {
	result := make(map[int64][]int32)
	linq.From(source).
		GroupByT(func(item dbModel.FoodTypeRel) int64 { return item.FoodId }, func(item dbModel.FoodTypeRel) int32 { return item.FoodTypeId }).
		SelectT(func(g linq.Group) linq.KeyValue {
		ints := make([]int32, 0)
		linq.From(g.Group).SelectT(func(item interface{}) int32 { return item.(int32) }).ToSlice(&ints)
		return linq.KeyValue{Key: g.Key, Value: ints}
	}).
		ToMap(&result)
	return result;
}

func toFoodMap(source []dto.UnitFood) map[int64]dto.UnitFood {
	foodMap := make(map[int64]dto.UnitFood, len(source))
	for _, e := range source {
		foodMap[e.Id] = e
	}
	return foodMap
}

func convert2FoodInFoodType(foodType dbModel.FoodType, rels map[int32][]int64, foods map[int64]dto.UnitFood) dto.FoodInFoodType {
	var foodIds []int64 = rels[foodType.Id]
	myFoods := make([]dto.UnitFood, 0, len(foodIds))
	for _, id := range foodIds {
		food,exists := foods[id]
		if(exists){
			myFoods = append(myFoods, food)
		}
	}
	return dto.FoodInFoodType{FoodType: foodType, Foods: myFoods}
}
